package com.scalesoft.artsync.plugins.exporter;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.json.JSONException;
import org.json.JSONObject;

import com.scalesoft.artsync.frontend.ProgressDialog;
import com.scalesoft.artsync.frontend.Progressable;
import com.scalesoft.artsync.model.CONTENTLEVEL;
import com.scalesoft.artsync.model.submission.Submission;
import com.scalesoft.artsync.model.submission.artwork.ArtworkSubmission;
import com.scalesoft.artsync.modelimpl.submission.SubmissionCollection;
import com.scalesoft.artsync.plugins.AccountSettings;
import com.scalesoft.artsync.plugins.Exporter;
import com.scalesoft.artsync.plugins.PluginRegistry;
import com.scalesoft.artsync.plugins.settings.SoFurrySettings;
import com.scalesoft.artsync.plugins.util.Log;
import com.scalesoft.artsync.plugins.util.ParserUtil;
import com.scalesoft.artsync.plugins.util.SoFurryOTP;
import com.scalesoft.artsync.util.Notifyable;

public class SoFurryExporter implements Exporter, Runnable, ActionListener {

	private Progressable progressHandler;
	private Notifyable notifyable;
	private boolean cancel;

	static {
		PluginRegistry.registerExporter(new SoFurryExporter());
	}

	@Override
	public String getPluginDisplayName() {
		return "SoFurry";
	}

	private String uploadSubmission(String serverUrl, Submission submission) throws Exception {
		File f = new File("files/" + ((ArtworkSubmission) submission).getFilename());

		// TODO: Apparently we should send some small request first in case we
		// don't have OTP credentials from the
		// server yet, so we don't have to upload the first image twice.

		HashMap<String, String> requestParameters = new HashMap<String, String>();
		requestParameters.put("f", "postBinary");
		requestParameters.put("id", ""); // We don't know the ID yet
		requestParameters.put("contentType", "artwork");
		requestParameters.put("cb", "0"); // This seems to be used by the site
											// javascript only
		String binaryUploadResponse = sendRequestOTP(serverUrl, requestParameters, f);

		Log.i(this, "Binary upload response: " + binaryUploadResponse);

		String id = ParserUtil.parseSection(binaryUploadResponse, "\"id\":", ",\"", 0);
		if (id == null) {
			Log.i(this, "Error on upload!");
			return null;
		}

		String width = ParserUtil.parseSection(binaryUploadResponse, "\"width\":", ",\"", 0);
		String height = ParserUtil.parseSection(binaryUploadResponse, "\"width\":", ",\"", 0);
		String filename = ParserUtil.parseSection(binaryUploadResponse, "\"filename\":\"", "\",\"", 0);
		String thumbnailFilename = ParserUtil.parseSection(binaryUploadResponse, "\"thumbnail\":\"", "\",\"", 0);

		int contentLevel = 1; // Default to adult so as to be sure.
		if (CONTENTLEVEL.CLEAN.equals(submission.getContentLevel()))
			contentLevel = 0;
		if (CONTENTLEVEL.ADULT.equals(submission.getContentLevel()))
			contentLevel = 1;
		if (CONTENTLEVEL.EXTREME.equals(submission.getContentLevel()))
			contentLevel = 2;

		requestParameters.clear();
		requestParameters.put("f", "postPage");
		requestParameters.put("name", submission.getTitle());
		requestParameters.put("keywords", submission.getTags());
		requestParameters.put("contentLevel", "" + contentLevel);
		requestParameters.put("content", submission.getDescription());
		requestParameters.put("contentPreview", submission.getDescription());
		requestParameters.put("timestamp", "" + System.currentTimeMillis());
		requestParameters.put("id", id);
		requestParameters.put("fileSize", "" + f.length());
		requestParameters.put("filename", filename);
		requestParameters.put("thumbnail", thumbnailFilename);
		requestParameters.put("origFilename", f.getName()); // TODO: is this
															// needed?
		requestParameters.put("width", width);
		requestParameters.put("height", height);
		requestParameters.put("accessLevel", "0");
		requestParameters.put("contentType", "artwork");
		requestParameters.put("display", "Y"); // TODO: Set to Y when sure that
												// this works
		requestParameters.put("enableComments", "1");
		requestParameters.put("enableVotes", "1");
		requestParameters.put("folder", "none");
		requestParameters.put("isHighlight", "0");
		requestParameters.put("order", "none");
		requestParameters.put("success", "true"); // How reassuring ;)

		String pageUploadResponse = sendRequestOTP(serverUrl, requestParameters, null);
		Log.i(this, "Page upload response: " + pageUploadResponse);
		return id;
	}

	private String sendRequestOTP(String requestUrl, HashMap<String, String> requestParameters, File f)
			throws Exception {
		// add authentication parameters to the request
		Map<String, String> authRequestParameters = SoFurryOTP.addAuthParametersToQuery(requestParameters);
		String httpResult = doPost(requestUrl, authRequestParameters, f);
		ArrayList<String> resultList = new ArrayList<String>();
		if (SoFurryOTP.parseResponse(httpResult) == false) {
			// Retry request with new otp sequence if it failed for the first
			// time
			authRequestParameters = SoFurryOTP.addAuthParametersToQuery(requestParameters);
			httpResult = doPost(requestUrl, authRequestParameters, f);
		}
		String errorMessage = parseErrorMessage(httpResult);
		return httpResult;
	}

	/**
	 * Utility function to post a form
	 * 
	 * @param requestUrl
	 * @param requestParameters
	 * @return result String with the server response
	 * @throws Exception
	 */
	private String doPost(String requestUrl, Map<String, String> requestParameters, File f) throws Exception {
		String lineEnd = "\r\n";
		String twoHyphens = "--";
		String boundary = "***232404jkg4220957934FW**";
		int bytesRead, bytesAvailable, bufferSize;
		byte[] buffer;
		int maxBufferSize = 1 * 1024 * 1024;

		URL url = new URL(requestUrl);
		HttpURLConnection conn2 = (HttpURLConnection) url.openConnection();
		conn2.setDoOutput(true);
		conn2.setDoInput(true);
		conn2.setUseCaches(false);

		conn2.setInstanceFollowRedirects(false);
		HttpURLConnection.setFollowRedirects(false);
		conn2.setRequestMethod("POST");
		conn2.setRequestProperty("Connection", "Keep-Alive");

		if (f == null)
			conn2.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
		else
			conn2.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + boundary);

		DataOutputStream dos = new DataOutputStream(conn2.getOutputStream());

		if (f != null) {
			Log.i(this, "Sending file: " + f.getName());
			FileInputStream fileInputStream = new FileInputStream(f);
			dos.writeBytes(twoHyphens + boundary + lineEnd);
			dos.writeBytes("Content-Disposition: form-data; name=\"file\";" + " filename=\"" + f.getName() + "\""
					+ lineEnd);
			dos.writeBytes(lineEnd);

			// create a buffer of maximum size

			bytesAvailable = fileInputStream.available();
			bufferSize = Math.min(bytesAvailable, maxBufferSize);
			buffer = new byte[bufferSize];

			// read file and write it into form...

			bytesRead = fileInputStream.read(buffer, 0, bufferSize);

			while (bytesRead > 0) {
				dos.write(buffer, 0, bufferSize);
				bytesAvailable = fileInputStream.available();
				bufferSize = Math.min(bytesAvailable, maxBufferSize);
				bytesRead = fileInputStream.read(buffer, 0, bufferSize);
			}

			// send multipart form data necesssary after file data...

			dos.writeBytes(lineEnd);
			dos.writeBytes(twoHyphens + boundary + twoHyphens + lineEnd);
			String encoding = "UTF-8";
			for (Map.Entry<String, String> parameter : requestParameters.entrySet()) {
				if (parameter != null) {
					dos.writeBytes("Content-Disposition: form-data; name=\"" + parameter.getKey() + "\"" + lineEnd);
					dos.writeBytes(lineEnd);
					dos.writeBytes(URLEncoder.encode(parameter.getValue(), encoding));
					dos.writeBytes(lineEnd);
					dos.writeBytes(twoHyphens + boundary + lineEnd);
				}
			}
		} else {
			StringBuilder request = new StringBuilder();
			boolean skipInitialAmpersand = true;
			String encoding = "UTF-8";
			for (Map.Entry<String, String> parameter : requestParameters.entrySet()) {
				if (parameter != null) {
					if (skipInitialAmpersand) {
						skipInitialAmpersand = false;
					} else {
						request.append("&");
					}
					request.append(parameter.getKey());
					request.append("=");
					if (parameter.getValue() != null)
						request.append(URLEncoder.encode(parameter.getValue(), encoding));
				}
			}

			Log.i(this, "Request: " + request);

			dos.writeBytes(request.toString());
		}

		dos.flush();
		dos.close();

		conn2.connect();

		InputStream is2 = conn2.getInputStream();
		StringBuilder result = new StringBuilder();
		int c;
		while ((c = is2.read()) != -1) {
			result.append((char) c);
		}
		Log.i(this, "Server response: " + result);
		return result.toString();
	}

	protected String parseErrorMessage(String httpResult) {
		try {
			// check for json error message and parse it
			// Log.i(this, "response: " + httpResult);

			// Only parse JSON responses
			if (httpResult.indexOf("<script type=\"text/javascript\">") > 0)
				return null;

			JSONObject jsonParser;
			jsonParser = new JSONObject(httpResult);
			int messageType = jsonParser.getInt("messageType");
			if (messageType == SoFurryOTP.AJAXTYPE_APIERROR) {
				String error = jsonParser.getString("error");
				Log.i(this, "Error: " + error);
				return error;
			}
		} catch (JSONException e) {
			Log.e(this, "Error in parseErrorMessage()", e);
		}

		return null;

	}

	@Override
	public void setProgressHandler(Progressable progressHandler) {
		this.progressHandler = progressHandler;
	}

	@Override
	public void run() {
		cancel = false;
		progressHandler.setActionListener(this);
		progressHandler.setMinimum(0);
		progressHandler.setMaximum(SubmissionCollection.getInstance().getSubmissions().size());
		progressHandler.setValue(0);
		progressHandler.show();
		String serverUrl = "http://chat.sofurry.com/ajaxfetch.php";
		SoFurrySettings settings = SoFurrySettings.getInstance();
		SoFurryOTP.username = settings.getUsername();
		SoFurryOTP.password = settings.getPassword();

		int curSubmission = 0;
		for (Submission submission : SubmissionCollection.getInstance().getSubmissions()) {
			try {
				if (cancel) {
					progressHandler.hide();
					return;
				}
				progressHandler.setValue(curSubmission++);
				if (submission.isSelected()) {
					if (submission.getIdForService(getPluginDisplayName()) == null
							|| submission.getIdForService(getPluginDisplayName()).trim().length() <= 0) {
						progressHandler.setMessage("Uploading " + submission.getTitle());
						String id = uploadSubmission(serverUrl, submission);
						// System.exit(0);
						if (id != null) {
							submission.setIdForService(getPluginDisplayName(), id);
							SubmissionCollection
									.saveSubmissionData(SubmissionCollection.getInstance().getSubmissions());
						}
					} else {
						Log.i(this, "Skipping because already uploaded: " + submission.getTitle() + " - "
								+ submission.getIdForService(getPluginDisplayName()));
					}
				}
			} catch (Exception e) {
				Log.e(this, "Error in run()", e);
			}
		}
		progressHandler.hide();
	}

	@Override
	public AccountSettings getSettings() {
		return SoFurrySettings.getInstance();
	}

	@Override
	public void actionPerformed(ActionEvent event) {
		if (event.getSource() == ProgressDialog.getInstance().getButtonCancel()) {
			cancel = true;
		}

	}

	@Override
	public void setNotifyable(Notifyable notifyable) {
		this.notifyable = notifyable;
	}

}
